/*
 * Copyright (C) 2012 David Bordoley
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package restlib.net;

import java.net.InetAddress;
import java.util.List;

import javax.annotation.concurrent.NotThreadSafe;

import restlib.impl.CommonParsers;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.net.InetAddresses;
import com.google.common.net.InternetDomainName;

/**
 * A builder for generating syntatically valid IRIs. IRIBuilder instances can be reused; 
 * it is safe to call build() multiple times to build multiple IRIs.
 */
@NotThreadSafe
public class IRIBuilder {  
    String fragment = "";
    String host = "";
    Path path = Path.of();
    Optional<Integer> port = Optional.absent();
    String query = "";
    String scheme = "";
    String userinfo = "";
    
    IRIBuilder(){}
    
    /**
     * Returns a new {@code IRI} instance.
     * @throws IllegalStateException If the IRI instance that would be generated by this 
     * builder is not a syntactically valid IRI.
     */
    public IRI build() {
        final IRI retval = new IRI(this);
        Preconditions.checkState(retval.isValid());
        return retval;
    }
    
    /**
     * Sets the authority component.
     * @param authority a non-null syntactically valid IRI authority component.
     * @return this {@code IRIBuilder} instance.
     * @throws NullPointerException if authority is null.
     * @throws IllegalArgumentException if authority is not a syntactically valid
     * IRI authority component.
     */
    public IRIBuilder setAuthority(final String authority) {
        Preconditions.checkNotNull(authority);
        final List<String> parts = NetParserImpl.parseAuthority(authority);
        
        final String userinfo = parts.get(0);
        final String host = parts.get(1);
        final String portStr = parts.get(2);

        // Make sure the authority component is actually valid
        Preconditions.checkArgument(
                host.isEmpty() ? (userinfo.isEmpty() && portStr.isEmpty()) : true);      
        return setUserinfo(userinfo)
                .setHost(host)
                .setPort(portStr.isEmpty() ? 
                        Optional.<Integer> absent() :
                            Optional.of(CommonParsers.parseUnsignedInteger(portStr)));        
    }
    
    /**
     * Sets the fragment component of the {@code IRIBuilder}.
     * @param fragment a valid IRI fragment string.
     * @return this {@code IRIBuilder} instance.
     * @throws NullPointerException if {@code fragment} is null.
     * @throws IllegalArgumentException if {@code fragment} contains
     * characters or code points not allowed in the IRI fragment component.
     */
    public IRIBuilder setFragment(final String fragment) {
        Preconditions.checkNotNull(fragment);
        Preconditions.checkArgument(
            IRIPredicates.IS_IFRAGMENT.apply(fragment));
        this.fragment = fragment;
        return this;
    }

    /**
     * Sets the host component of the {@code IRIBuilder} from an
     * {@code InetAddress}.
     * @param host any non-null IPv4 or IPv6 address.
     * @return this {@code IRIBuilder} instance.
     * @throws NullPointerException if host is null. 
     */
    public IRIBuilder setHost(final InetAddress host) {
        Preconditions.checkNotNull(host);
        this.host = InetAddresses.toUriString(host);
        return this;
    }
    
    /**
     * Sets the host component of the {@code IRIBuilder} from an
     * {@code InternetDomainName}.
     * @param host any non-null domain name including international domain names.
     * @return this {@code IRIBuilder} instance.
     * @throws NullPointerException if host is null. 
     */
    public IRIBuilder setHost(final InternetDomainName host) {
        Preconditions.checkNotNull(host);
        this.host = host.name();
        return this;
    }
    
    /**
     * Sets the host of the {@code IRIBuilder}. 
     * @param host any valid IRI host component.
     * @return this {@code IRIBuilder} instance.
     * @throws NullPointerException if host is null. 
     * @throws IllegalArgumentException if host is not a valid IRI host.
     */
    public IRIBuilder setHost(final String host) {
        Preconditions.checkNotNull(host);
        if (host.isEmpty()) {
            this.host = host;
            return this;
        } 

        try {
            InternetDomainName.from(host);
            this.host = host;
            return this;
        } catch (final IllegalArgumentException e) {}
        
        try {
            InetAddresses.forUriString(host);
            this.host = host;
            return this;
        } catch (final IllegalArgumentException e) {}

        throw new IllegalArgumentException("Invalid host specifier: " + host);
    }
 
    /**
     * Sets the specific path component of the {@code IRIBuilder}.
     * @param path a {@code Path} instance that is a valid IRI path.
     * @return this {@code IRIBuilder} instance.
     * @throws NullPointerException if {@code path} is null.
     * @throws IllegalArgumentException if {@code path} is not a valid IRI path.
     */
    public IRIBuilder setPath(final Path path) {
        Preconditions.checkNotNull(path);
        Preconditions.checkArgument(path.isIRIPath());
        this.path = path;
        return this;
    }

    /**
     * Sets the path component of the {@code IRIBuilder}.
     * @param path a {@code String} instance that is a valid IRI path.
     * @return this {@code IRIBuilder} instance.
     * @throws NullPointerException if {@code path} is null.
     * @throws IllegalArgumentException if {@code path} is not a valid IRI path.
     */
    public IRIBuilder setPath(final String path) {
        return setPath(Path.parse(path));
    }

    /**
     * Set the port component of the {@code IRIBuilder}.
     * @param port any valid TCP port number.
     * @return this {@code IRIBuilder} instance.
     * @throws IllegalArgumentException if {@code port} is not a valid TCP port number.
     */
    public IRIBuilder setPort(final int port) {
        return setPort(Optional.of(port));
    }
    
    IRIBuilder setPort(final Optional<Integer> port) {
        Preconditions.checkNotNull(port);
        Preconditions.checkArgument(
                port.isPresent() ? (port.get() > 0) && (port.get() < 65536 ) : true);
        this.port = port;
        return this;
    }

    /**
     * Sets the query component of the {@code IRIBuilder}.
     * @param query a valid IRI query string
     * @return this {@code IRIBuilder} instance.
     * @throws NullPointerException if {@code query} is null.
     * @throws IllegalArgumentException if {@code query} contains
     * characters or code point not allowed in the IRI query component.
     */
    public IRIBuilder setQuery(final String query) {
        Preconditions.checkNotNull(query);
        Preconditions.checkArgument(
            IRIPredicates.IS_IQUERY.apply(query));
        this.query = query;
        return this;
    }

    /**
     * Sets the scheme component of the {@code IRIBuilder}.
     * @param scheme a valid IRI scheme.
     * @return this {@code IRIBuilder} instance.
     * @throws NullPointerException if {@code scheme} is null.
     * @throws IllegalArgumentException if {@code scheme} is not a 
     * syntatically valid IRI scheme.
     */
    public IRIBuilder setScheme(final String scheme) {
        Preconditions.checkNotNull(scheme);
        Preconditions.checkArgument(
                scheme.isEmpty() |
                IRIPredicates.IS_SCHEME.apply(scheme));
        this.scheme = scheme;
        return this;
    }

    /**
     * Sets the userinfo component of the {@code IRIBuilder}.
     * @param userinfo a valid IRI userinfo component.
     * @return this {@code IRIBuilder} instance.
     * @throws NullPointerException if {@code userinfo} is null.
     * @throws IllegalArgumentException if {@code userinfo} is not a 
     * syntatically valid IRI userinfo component.
     */
    public IRIBuilder setUserinfo(final String userinfo) {
        Preconditions.checkNotNull(userinfo);
        Preconditions.checkArgument(IRIPredicates.IS_IUSERINFO.apply(userinfo));
        this.userinfo = userinfo;
        return this;
    }   
}
